package com.digua.base.socket.dispatcher

import android.text.TextUtils
import com.digua.base.socket.SocketListener
import com.digua.base.socket.response.ErrorResponse
import com.digua.base.socket.util.ThreadUtil
import org.java_websocket.framing.Framedata
import java.nio.ByteBuffer
import java.util.ArrayDeque
import java.util.Queue


class MainThreadResponseDelivery : ResponseDelivery {

    companion object {
        /**
         * Listener 操作锁
         */
        private val LISTENER_BLOCK = Any()
        private var RUNNABLE_POOL: Queue<CallbackRunnable<*>> = ArrayDeque(5)
    }

    private val mSocketListenerList: MutableList<SocketListener> = ArrayList()


    override fun addListener(listener: SocketListener) {
        if (!mSocketListenerList.contains(listener)) {
            synchronized(LISTENER_BLOCK) { mSocketListenerList.add(listener) }
        }
    }

    override fun removeListener(listener: SocketListener) {
        if (isEmpty()) return
        if (mSocketListenerList.contains(listener)) {
            synchronized(LISTENER_BLOCK) { mSocketListenerList.remove(listener) }
        }
    }

    override fun onConnected() {
        if (isEmpty()) return
        if (ThreadUtil.checkMainThread()) {
            synchronized(LISTENER_BLOCK) {
                for (listener in mSocketListenerList) {
                    listener.onConnected()
                }
            }
        } else {
            val callbackRunnable: CallbackRunnable<Any> = getRunnable()
            callbackRunnable.type = RUNNABLE_TYPE.CONNECTED
            callbackRunnable.mSocketListenerList = mSocketListenerList
            ThreadUtil.runOnMainThread(callbackRunnable)
        }
    }

    override fun onConnectFailed(cause: Throwable?) {
        if (isEmpty()) return
        if (ThreadUtil.checkMainThread()) {
            synchronized(LISTENER_BLOCK) {
                for (listener in mSocketListenerList) {
                    listener.onConnectFailed(cause)
                }
            }
        } else {
            val callbackRunnable: CallbackRunnable<Any> = getRunnable()
            callbackRunnable.type = RUNNABLE_TYPE.CONNECT_FAILED
            callbackRunnable.connectErrorCause = cause
            callbackRunnable.mSocketListenerList = mSocketListenerList
            ThreadUtil.runOnMainThread(callbackRunnable)
        }
    }

    override fun onDisconnect() {
        if (isEmpty()) return
        if (ThreadUtil.checkMainThread()) {
            synchronized(LISTENER_BLOCK) {
                for (listener in mSocketListenerList) {
                    listener.onDisconnect()
                }
            }
        } else {
            val callbackRunnable: CallbackRunnable<Any> = getRunnable()
            callbackRunnable.type = RUNNABLE_TYPE.DISCONNECT
            callbackRunnable.mSocketListenerList = mSocketListenerList
            ThreadUtil.runOnMainThread(callbackRunnable)
        }
    }

    override fun onSendDataError(errorResponse: ErrorResponse?) {
        if (isEmpty() || errorResponse == null) return
        if (ThreadUtil.checkMainThread()) {
            synchronized(LISTENER_BLOCK) {
                for (listener in mSocketListenerList) {
                    listener.onSendDataError(errorResponse)
                }
            }
        } else {
            val callbackRunnable: CallbackRunnable<Any> = getRunnable()
            callbackRunnable.type = RUNNABLE_TYPE.SEND_ERROR
            callbackRunnable.errorResponse = errorResponse
            callbackRunnable.mSocketListenerList = mSocketListenerList
            ThreadUtil.runOnMainThread(callbackRunnable)
        }
    }

    override fun <T> onMessage(message: String, data: T?) {
        if (isEmpty() || message == null) return
        if (ThreadUtil.checkMainThread()) {
            synchronized(LISTENER_BLOCK) {
                for (listener in mSocketListenerList) {
                    listener.onMessage(message, data)
                }
            }
        } else {
            val callbackRunnable: CallbackRunnable<T> = getRunnable()
            callbackRunnable.type = RUNNABLE_TYPE.STRING_MSG
            callbackRunnable.textResponse = message
            callbackRunnable.formattedData = data
            callbackRunnable.mSocketListenerList = mSocketListenerList
            ThreadUtil.runOnMainThread(callbackRunnable)
        }
    }

    override fun <T> onMessage(bytes: ByteBuffer, data: T?) {
        if (isEmpty() || bytes == null) return
        if (ThreadUtil.checkMainThread()) {
            synchronized(LISTENER_BLOCK) {
                for (listener in mSocketListenerList) {
                    listener.onMessage(bytes, data)
                }
            }
        } else {
            val callbackRunnable: CallbackRunnable<T> = getRunnable()
            callbackRunnable.type = RUNNABLE_TYPE.BYTE_BUFFER_MSG
            callbackRunnable.byteResponse = bytes
            callbackRunnable.formattedData = data
            callbackRunnable.mSocketListenerList = mSocketListenerList
            ThreadUtil.runOnMainThread(callbackRunnable)
        }
    }

    override fun onPing(framedata: Framedata?) {
        if (isEmpty()) return
        if (ThreadUtil.checkMainThread()) {
            synchronized(LISTENER_BLOCK) {
                for (listener in mSocketListenerList) {
                    listener.onPing(framedata)
                }
            }
        } else {
            val callbackRunnable: CallbackRunnable<Any> = getRunnable()
            callbackRunnable.type = RUNNABLE_TYPE.PING
            callbackRunnable.framedataResponse = framedata
            callbackRunnable.mSocketListenerList = mSocketListenerList
            ThreadUtil.runOnMainThread(callbackRunnable)
        }
    }

    override fun onPong(framedata: Framedata?) {
        if (isEmpty()) return
        if (ThreadUtil.checkMainThread()) {
            synchronized(LISTENER_BLOCK) {
                for (listener in mSocketListenerList) {
                    listener.onPong(framedata)
                }
            }
        } else {
            val callbackRunnable: CallbackRunnable<Any> = getRunnable()
            callbackRunnable.type = RUNNABLE_TYPE.PONG
            callbackRunnable.framedataResponse = framedata
            callbackRunnable.mSocketListenerList = mSocketListenerList
            ThreadUtil.runOnMainThread(callbackRunnable)
        }
    }

    override fun clear() {
        if (mSocketListenerList.isNotEmpty()) {
            synchronized(LISTENER_BLOCK) { mSocketListenerList.clear() }
        }
    }

    override fun isEmpty() = mSocketListenerList.isEmpty()

    private fun <T> getRunnable() = (RUNNABLE_POOL.poll() ?: CallbackRunnable<T>()) as CallbackRunnable<T>

    enum class RUNNABLE_TYPE {
        NON,  //未设置
        CONNECTED,  //链接成功
        CONNECT_FAILED,  //链接失败
        DISCONNECT,  //链接断开
        SEND_ERROR,  //数据发送失败
        STRING_MSG,  //接收到 String 数据
        BYTE_BUFFER_MSG,  //接收到 ByteBuffer 数据
        PING,  //接收到 Ping
        PONG //接收到 Pong
    }

    /**
     * 避免频繁创建 Runnable 对象造成的内存浪费，
     * 故此处使用可重用的 Runnable
     */
    private class CallbackRunnable<T> : Runnable {
        var mSocketListenerList: List<SocketListener>? = null
        var errorResponse: ErrorResponse? = null
        var connectErrorCause: Throwable? = null
        var textResponse: String? = null
        var byteResponse: ByteBuffer? = null
        var framedataResponse: Framedata? = null
        var formattedData: T? = null
        var type: RUNNABLE_TYPE = RUNNABLE_TYPE.NON

        override fun run() {
            try {
                if (type == RUNNABLE_TYPE.NON || mSocketListenerList?.isEmpty() == true) return
                if (type == RUNNABLE_TYPE.CONNECT_FAILED && connectErrorCause == null) return
                if (type == RUNNABLE_TYPE.SEND_ERROR && errorResponse == null) return
                if (type == RUNNABLE_TYPE.STRING_MSG && TextUtils.isEmpty(textResponse)) return
                if (type == RUNNABLE_TYPE.BYTE_BUFFER_MSG && byteResponse == null) return
                if (type == RUNNABLE_TYPE.PING && framedataResponse == null) return
                if (type == RUNNABLE_TYPE.PONG && framedataResponse == null) return
                synchronized(LISTENER_BLOCK) {
                    mSocketListenerList?.let {
                        when (type) {
                            RUNNABLE_TYPE.CONNECTED -> for (listener in it) {
                                listener.onConnected()
                            }

                            RUNNABLE_TYPE.CONNECT_FAILED -> for (listener in it) {
                                listener.onConnectFailed(connectErrorCause)
                            }

                            RUNNABLE_TYPE.DISCONNECT -> for (listener in it) {
                                listener.onDisconnect()
                            }

                            RUNNABLE_TYPE.SEND_ERROR -> for (listener in it) {
                                listener.onSendDataError(errorResponse)
                            }

                            RUNNABLE_TYPE.STRING_MSG -> for (listener in it) {
                                listener.onMessage(textResponse ?: "", formattedData)
                            }

                            RUNNABLE_TYPE.BYTE_BUFFER_MSG -> for (listener in it) {
                                byteResponse?.let { listener.onMessage(it, formattedData) }
                            }

                            RUNNABLE_TYPE.PING -> for (listener in it) {
                                listener.onPing(framedataResponse)
                            }

                            RUNNABLE_TYPE.PONG -> for (listener in it) {
                                listener.onPong(framedataResponse)
                            }

                            else -> {}
                        }
                    }
                    mSocketListenerList = null
                    errorResponse = null
                    connectErrorCause = null
                    textResponse = null
                    byteResponse = null
                    framedataResponse = null
                    formattedData = null
                }
            } finally {
                RUNNABLE_POOL.offer(this)
            }
        }
    }
}